import os, sys
import argparse
from datetime import datetime, time, date

__project__ = "BL-420N_visualisation"
__version__ = "1.3.0"
__author__ = "hammerklavier@noreply.gitcode.com"
__email__ = "q5vsx3@163.com"
__copyright__ = "Copyright (c) 2025 hammerklavier@noreply.gitcode.com"
__license__ = "GNU GENERAL PUBLIC LICENSE Version 3"
__credits__ = ["RIBBON"]
__status__ = "Prototype"
__issue__ = "https://gitcode.com/hammerklavier/BL-420N_visualisation/issues"

def parse_time(s: str, parser: argparse.ArgumentParser) -> datetime:
    try:
        dt = datetime.combine(
            date.today(),
            time.fromisoformat(s)
        )
    except Exception:
        parser.error(
            """--add-incident doesn't get valid datetime format!
        Run `python -c 'from datetime import time;help(time.isoformat)'`
        for more information."""
        )
        exit(1)
    return dt

def no_gui_plot(parser: argparse.ArgumentParser):

    print(
f"""{__project__} v{__version__}, by {__author__}.
This project follows {__license__}.
If you find any bugs, please report it to {__issue__}
""")

    # Parse arguments
    # parser = argparse.ArgumentParser()

    args = parser.parse_args()

    # check args
    # file path check
    for file_path in args.input:
        if os.path.isfile(file_path) or os.path.islink(file_path):
            file_path = os.path.realpath(file_path)
        else:
            parser.error("--input doesn't get valid file path!")
    # input file -- label number check
    if args.label:
        if len(args.label) != len(args.input):
            parser.error("--label doesn't match with number of input files!")
    # start/end time check
    if args.start is not None and args.end is not None:
        try:
            start_time = datetime.combine(
                date.today(), time.fromisoformat(args.start))
            end_time = datetime.combine(
                date.today(), time.fromisoformat(args.end))
        except Exception:
            parser.error(
                """--start/--end doesn't get valid datetime format!
            Run `python -c 'from datetime import time;help(time.isoformat)'`
            for more information."""
            )
    if args.add_incident is not None:
        if len(args.add_incident) % 2 != 0:
            parser.error("--add_incident should have even numbers of arguments!")

    import matplotlib as mpl
    from matplotlib import font_manager
    import numpy as np
    import polars as pl
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    import mplfonts
    import seaborn as sns
    from libs.reverse_raw_channel_data_to_csv import native_read_raw_data

    # seaborn setting must be placed ahead, otherwise
    # customised font settings will be covered.
    sns.set_theme(style="whitegrid")

    if os.name == "nt":
        mpl.use("QTAgg")
    elif os.name == "posix":
        try:
            mpl.use("GTK4Agg")
        except Exception:
            print(
                "Warning: Cannot use GTK4Agg as matplotlib backend. Fall back to QTAgg."
            )
            mpl.use("QTAgg")
    # set up fonts
    ## add fonts (if Linux system)
    if os.name == "posix":
        print("Add fonts")
        home_dir = os.path.expanduser("~")
        dirs = [f'{home_dir}/.fonts', f'{home_dir}/.Fonts',
                f'{home_dir}/.local/share/fonts', '/usr/share/fonts']
        for dir in dirs:
            files_triple = os.walk(dir)
            for file_path, _, files in files_triple:
                for file in files:
                    if file.lower().endswith(".ttf") or file.lower().endswith(".otf") or file.lower().endswith(".ttc"):
                        if args.verbose:
                            print("find font:", file)
                        try:
                            font_manager.fontManager.addfont(
                                os.path.join(file_path, file))
                        except RuntimeError as e:
                            if args.verbose:
                                print("Error:", e, file)

    ## Assign HarmonyOS Sans as default fonts.
    ## They are placed under `fonts` directory.
    try:
        font_manager.fontManager.addfont("../fonts/HarmonyOS_Sans_SC_Regular.ttf")
        font_manager.fontManager.addfont("../fonts/HarmonyOS_Sans_SC_Black.ttf")
        font_manager.fontManager.addfont("../fonts/HarmonyOS_Sans_SC_Bold.ttf")
        font_manager.fontManager.addfont("../fonts/HarmonyOS_Sans_SC_Light.ttf")
        font_manager.fontManager.addfont("../fonts/HarmonyOS_Sans_SC_Medium.ttf")
        font_manager.fontManager.addfont("../fonts/HarmonyOS_Sans_SC_Thin.ttf")
    except Exception as err:
        print(f"Warning: {err}")
    finally:
        mplfonts.use_font("HarmonyOS Sans SC")

    # convert all input files into dataframe
    collection: list[tuple[pl.DataFrame, str, str]] = []
    for file_path in args.input:
        df, unit, project_name = native_read_raw_data(file_path)
        collection.append((df, unit, project_name))

    # plot result
    plt.figure(figsize=(15, 5))

    # prepare for ylim
    y_quantile_min: float = np.inf
    y_quantile_max: float = -np.inf

    # plot data
    for i, (df, unit, project_name) in enumerate(collection):
        df = df.filter((pl.col("timestamp") >= start_time) & (  # type: ignore
            pl.col("timestamp") <= end_time))  # type: ignore
        columns = df.columns
        x = df.get_column(columns[0])
        y = df.get_column(columns[1])
        y_quantile_min = min(y_quantile_min, y.quantile(
            float(args.y_quantile_range[0])))  # type: ignore
        y_quantile_max = max(y_quantile_max, y.quantile(
            float(args.y_quantile_range[1])))  # type: ignore

        # set legend
        if args.label:
            label = args.label[i]
        else:
            label = f"{project_name} [{unit}]"

        plt.plot(x, y, label=label)

        # rotate x-axis
        plt.xticks(rotation=15)

        if args.incident:
            incident = df.drop_nulls().select(columns[0], columns[2])
            for row in incident.iter_rows():
                plt.axvline(row[0], label=row[1], color="k")

    if y_range := args.y_value_range:
        print(y_range)
        plt.ylim(float(y_range[0]), float(y_range[1]))
    else:
        plt.ylim(y_quantile_min - (y_quantile_max - y_quantile_min)*0.2,
                 y_quantile_max + (y_quantile_max - y_quantile_min)*0.2)

    if args.add_incident:
        _, y_max = plt.gca().get_ylim()
        for i in range(0, len(args.add_incident), 2):
            slice = args.add_incident[i:i+2]
            incident_time = parse_time(slice[0], parser)
            label_txt = slice[1]

            plt.axvline(
                incident_time, # type: ignore
                color="k"
            )
            plt.text(
                incident_time, y_max, # type: ignore
                label_txt,
                verticalalignment="top",
                horizontalalignment="right",
                rotation=90
            )

    # set x/y labels and title
    if args.xlabel:
        plt.xlabel(args.xlabel)
    if args.ylabel:
        plt.ylabel(args.ylabel)
    if args.title:
        plt.title(args.title)

    plt.legend()
    plt.tight_layout()
    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
    if args.output:
        plt.savefig(args.output, dpi=args.dpi)

    plt.show()

    sys.exit()
